//
// Created by whg on 2025-06-01.
//

#include <time.h>

#include "binaryFileParser.hpp"
#include "object/pyInteger.hpp"
#include "runtime/global.hpp"

BinaryFileParser::BinaryFileParser(BufferedInputStream *input) : _input(input) {

}

CodeObject* BinaryFileParser::parse() {
    int magicNumber = _input->readInt();
    printf("magic number is 0x%x\n", magicNumber);

    int fileFlag = _input->readInt();

    auto modifyDate = (time_t)_input->readInt();
    struct tm * timeInfo = localtime(&modifyDate);
    char buffer[80];
    strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeInfo);
    printf("%s\n", buffer);

    int fileSize = _input->readInt();

    char objectType = _input->read();
    bool isRefFlag = checkRefFlag(objectType);
    objectType = clearHighBit(objectType);
    if(objectType == 'c'){
        int index = 0;
        if(isRefFlag){
            index = _cache.length();
            _cache.add(nullptr);
        }
        CodeObject* result = getCodeObject();
        if(isRefFlag){
            _cache.set(index, result);
        }

        return result;
    }

    return nullptr;
}

CodeObject* BinaryFileParser::getCodeObject() {
    int argCount = _input->readInt();
    int posArgCount = _input->readInt();
    int keyArgCount = _input->readInt();
    int nLocals = _input->readInt();
    int stackSize = _input->readInt();
    int flags = _input->readInt();

    PyString* byteCodes = getByteCodes();
    PyList* consts = getConsts();
    PyList* names = getNames();
    PyList* varNames = getVarNames();
    PyList* freeVars = getFreeVars();
    PyList* cellVars = getCellVars();

    PyString* fileName = getFileName();
    PyString* moduleName = getName();
    int lineno = _input->readInt();
    PyString* notable = getNoTable();

    return new CodeObject(argCount, posArgCount, keyArgCount, nLocals, stackSize, flags,
            byteCodes, consts, names, varNames, freeVars,
            cellVars, fileName, moduleName, lineno, notable);
}

PyString* BinaryFileParser::getByteCodes() {
    char objectType = _input->read();
    bool isRefFlag = checkRefFlag(objectType);
    objectType = clearHighBit(objectType);

    if(objectType != 's'){
        printf("expect a string for byte codes, but got %c\n", objectType);
        return nullptr;
    }

    PyString* str = getStrint(true);
    if(isRefFlag){
        _cache.add(str);
    }
    return str;
}

PyString* BinaryFileParser::getStrint(bool isLongString) {
    unsigned int length = isLongString ? _input->readInt() : _input->read();
    char* value = new char[length];
    for(int i=0;i<length;i++){
        value[i] = _input->read();
    }
    PyString* str = new PyString(value, length);
    delete[] value;
    return str;
}

PyList* BinaryFileParser::getConsts(){
    return tryToGetTuple();
}

PyList* BinaryFileParser::getNames(){
    return tryToGetTuple();
}

PyList* BinaryFileParser::getVarNames(){
    return tryToGetTuple();
}

PyList* BinaryFileParser::getFreeVars(){
    return tryToGetTuple();
}

PyList* BinaryFileParser::getCellVars(){
    return tryToGetTuple();
}

PyList* BinaryFileParser::tryToGetTuple(){
    char objectType = _input->read();
    bool isRefFlag = checkRefFlag(objectType);
    objectType = clearHighBit(objectType);

    PyList* result = nullptr;
    if(objectType == ')'){
        result = getTuple();
        if(isRefFlag){
            _cache.add(result);
        }
    }else if(objectType == 'r'){
        int index = _input->readInt();
        // result = dynamic_cast<PyList*>(_cache.get(index));
        result = static_cast<PyList*>(_cache.get(index));
    }else{
       _input->unread();
       printf("tryToGetTuple Unknown objectType=%c\n", objectType);
    }
    return result;
}

PyList* BinaryFileParser::getTuple(){
    unsigned char length = _input->read();
    PyList* list = new PyList();

    for(int i=0;i<length;i++){
        char objectType = _input->read();
        bool isRefFlag = checkRefFlag(objectType);
        objectType = clearHighBit(objectType);
        int index = 0;

        PyObject* obj = nullptr;
        switch (objectType) {
            case 'c':
                if(isRefFlag){
                    index = _cache.length();
                    _cache.add(nullptr);
                }
                obj = getCodeObject();
                if(isRefFlag){
                    _cache.set(index, obj);
                }
                break;
            case 'i':
                obj = new PyInteger(_input->readInt());
                break;
            case 'N':
                // list->add(Global::PyNone);
                obj = Global::PyNone;
                break;
            case 'T':
                obj = Global::PyTrue;
                break;
            case 'F':
                obj = Global::PyFalse;
                break;
            case 't':
                obj = getStrint(true);
                break;
            case 'Z':
                obj = getStrint(false);
                break;
            case 'z':
                obj = getStrint(false);
                break;
            case 's':
                obj = getStrint(true);
                break;
            case 'R':
                obj = _stringTable.get(_input->readInt());
                break;
            case 'r':
                obj = _cache.get(_input->readInt());
                break;
            case ')':
                obj = getTuple();
                break;
            default:
                printf("getTuple Unknown objectType=%c\n", objectType);
        }

        list->append(obj);
        if(isRefFlag && objectType != 'c'){
            _cache.add(obj);
        }
    }

    // 之前忘记写了return也能在-O0（无优化）下“巧合”的执行，或许是因为list恰好用到EAX寄存器
    return list;
}

PyString* BinaryFileParser::getFileName(){
    return getName();
}

PyString* BinaryFileParser::getName(){
    char objectType = _input->read();
    bool isRefFlag = checkRefFlag(objectType);
    objectType = clearHighBit(objectType);

    PyString* str = nullptr;
    switch (objectType) {
        case 's':
            str = getStrint(true);
            break;
        case 't':
            str = getStrint(true);
            break;
        case 'z':
            str = getStrint(false);
            break;
        case 'Z':
            str = getStrint(false);
            break;
        case 'R':
            str = _stringTable.get(_input->readInt());
            break;
        case 'r':
            str = static_cast<PyString*>(_cache.get(_input->readInt()));
            break;
        default:
            printf("getName Unknown objectType=%c\n", objectType);
    }

    if(isRefFlag){
        _cache.add(str);
    }

    return str;
}

PyString* BinaryFileParser::getNoTable(){
    char objectType = _input->read();
    bool isRefFlag = checkRefFlag(objectType);
    objectType = clearHighBit(objectType);

    if(objectType != 's' && objectType != 't'){
        printf("expect a string for no table, but got %c\n", objectType);
        return nullptr;
    }

    return getStrint(true);
}
